package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: xie.zh
 * @create: 2021.03.24   09:10
 * @version: 1.0
 */
@Component
@Aspect
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RBloomFilter bloomFilter;

    @Around("@annotation(GmallCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取方法对象
        Method method = signature.getMethod();
        //获取特定方法上的注解
        GmallCache annotation = method.getAnnotation(GmallCache.class);
        //获取方法参数
        List<Object> args = Arrays.asList(joinPoint.getArgs());


        //获取key
        String prefix = annotation.prefix();
        String key = prefix + args;

        //通过bloomFilter判断数据是否存在
        if (!this.bloomFilter.contains(key)) {
            return null;
        }

        //查询缓存 命中缓存直接返回
        String json = this.redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)) {
            return JSON.parseObject(json, signature.getReturnType());
        }

        //为了防止缓存击穿添加分布式锁
        String lock_prefix = annotation.lock();
        RLock lock = this.redissonClient.getLock(lock_prefix + args);
        lock.lock();

        try {
            //再查缓存
            String json2 = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)) {
                return JSON.parseObject(json2, signature.getReturnType());
            }
            //远程调用或者查询数据库
            Object result = joinPoint.proceed(joinPoint.getArgs());

            //放入缓存 释放分布式锁
            int timeOut = annotation.timeout() + new Random().nextInt(annotation.random());
            this.redisTemplate.opsForValue().set(key, JSON.toJSONString(result), timeOut
                    , TimeUnit.MINUTES);
            return result;
        } finally {
            lock.unlock();
        }
    }
}
